///////////////////////////////////////////////////////////////////////////////
//	FILENAME:	eTImage.m 
//	SUMMARY:	Implementation of Image annotations with Action support
//	SUPERCLASS:	Object
//	INTERFACE:	None
//	PROTOCOLS:	<Annotation,HTMDSupport,ASCIISupport,LaTeXSupport,Tool,
//				InspectableTarget,DocNotification>
//	AUTHOR:		Rohit Khare
//	COPYRIGHT:	(c) 1994 California Institure of Technology, eText Project
///////////////////////////////////////////////////////////////////////////////
//	IMPLEMENTATION COMMENTS
//		A whole lot of interesting delegation has been introduced between
//	eTImage and eTImageComponent.
//		Tips for subclass implementors:
//	* Feel free to access the ivars directly and avoid the extra messages...
//	* ... but make sure to call updateGraphics to commit your changes.
//	* YOU have to make sure the images are deallocated. Leave all of the other
//		ivars alone in this class, but if you want to free these images, YOU
//		have to free them in your subclass's -free.
///////////////////////////////////////////////////////////////////////////////
//	HISTORY
//	02/14/95:	In TestTwentyFourBitRGB on mono, it kept crashing on -backColor
//	02/11/95:	Attempted to modify captioning process to avoid redraw.
//	10/30/94:	Modified to support <InspectableTarget>
//	07/16/94:	Rewritten from scratch, integrating ImageAnnotation and Image
///////////////////////////////////////////////////////////////////////////////

#import "eTImage.h"
#import "eTImageUI.h"
#define _eTImageVERSION	10
#define _eTImageVERSION_captions	22

@implementation eTImage
//	id		theText
//	id		etDoc
//	
//	id		imageComponent
//	id		image
//	id		altImageComponent
//	id		altImage
//	char 	*description
//	NXSize	size
//	BOOL	usesButtonStyle
//	BOOL	isDraggable
//	BOOL	highlighted

// "Delegate" methods 
	// User Interaction

- click:(NXEvent*)e
	{ return self; }
- doubleClick:(NXEvent*)e
{	
	NXAtom fname = [imageComponent currentPath];

	if (*fname && !access(fname, F_OK|R_OK)) {
		// we need to flip the location y coord
		NXPoint pt;
		NXSize	sz;
		id 		img;
		
		pt = e->location;
		img = [imageComponent icon];
		[theText convertPoint:&pt fromView:nil];
		[img getSize:&sz];
		pt.x -= sz.width/2;
		pt.y += sz.height/2;
		[[Application workspace]
			openFile:fname fromImage: img
			at: &pt inView:theText];
	}
	return self; 
}

- inspect:(NXEvent*)e
{
	[[NXApp inspector] inspect:self];
	return self; 
}
- (id <Inspectable>) inspectableDelegate {
	return [[eTImageUI new] setAnnotation:self]; }

- drag: (Pasteboard *)draggingPboard image:(NXImage **)proxyImage
{
	NXAtom fname = [imageComponent currentPath];

	if (*fname && !access(fname, F_OK|R_OK)) {
		[imageComponent writeComponentToPboard:draggingPboard];
		*proxyImage = [imageComponent icon];
	} else {
		NXRunAlertPanel("eTImage","Cannot access %s. Try saving the document.","OK",NULL,NULL,fname?fname:"the image");
		draggingPboard=nil;
		*proxyImage=nil;
	}
	return self;
}

// "Client" methods -- feel free to access ivars directly.
// Getter/Setter methods

- setImageComponent: newImageComponent
{
	if (![newImageComponent isKindOf:[eTImageComponent class]])
		return nil;
	if (imageComponent != newImageComponent) {
		[imageComponent free];
		imageComponent = newImageComponent;
	}
	image = [imageComponent theImage];
	if (image) [image getSize:&size];
	else size.width = size.height = 0.0;
	captionMode = NO;
	// don't drag out shared images. Conflict with subclasses that drag out other data.
	// Reveals design flaw.
	if ([imageComponent isShared] && ([self class] == [eTImage class])) isDraggable = NO;
	return self;
}
- setAltImageComponent: newAltImageComponent
{
	if (![newAltImageComponent isKindOf:[eTImageComponent class]])
		return nil;
	altImageComponent = newAltImageComponent;
	altImage = [altImageComponent theImage];
	return self;
}
- setDescription: (const char *) newDescription
{	
	description=realloc(description,(strlen(newDescription)+1)*sizeof(char));
	strcpy(description,newDescription);
	if (captionMode) [self setCaptionMode];
	[etDoc touch];
	return self;
}
- setUsesButtonStyle:(BOOL)newState
{
	usesButtonStyle = newState; 
	return self;
}
- setDraggable:(BOOL)newState
{
	isDraggable=newState;
	return self;
}
- setSize:(const NXSize *)newSize
{
	// I don't quite know what to do with this.
	// How do we factor in the Component's isMutable semantics?
	// should this do a touch?
	if ([imageComponent isMutable] && !captionMode) {
		size = *newSize;
		[image setSize:&size];
		//[imageComponent touch];
		if ([altImageComponent isMutable]) {
			[altImage setSize:&size];
			//[altImageComponent touch];
		}
		[etDoc touch];
	}
	return self;
}

- setCaptionMode {
	if (description && theText) {
		NXRun *theRun = [theText runForAnnotation:self];

		if (theRun && description) {
			size.width = [theRun->font getWidthOf:description];
			size.height = [theRun->font pointSize];
			captionMode = YES;
			[self setUsesButtonStyle: YES];
		} else if (description) {
			size.width = [[theText font] getWidthOf:description];
			size.height = [[theText font] pointSize];
			captionMode = YES;
			[self setUsesButtonStyle: YES];
		}
	}
	return self;
}
- setState:(BOOL) newState {state = newState; return [self updateGraphics];}
- eTextObj 
	{return theText;}
- etDoc 
	{return etDoc;}
- imageComponent
	{return imageComponent;}
- altImageComponent 
	{return altImageComponent;}
- (char *) description 
	{return description;}
- (char **) getDescription 				// be careful of reallocing this
	{return &description;}
- (BOOL) usesButtonStyle 
	{return usesButtonStyle;}
- (BOOL) isDraggable 
	{return isDraggable;}
- (NXSize *) size
	{return &size;}
- (BOOL) state {return state;}
- (BOOL) captionMode {return captionMode;}
// State Flushing
- updateGraphics
{
	[theText perform:@selector(calcLine) with:nil afterDelay:0 cancelPrevious:YES];
	[[theText superview] perform:@selector(display) with:nil afterDelay:0 cancelPrevious:YES];
	return self;
}

// "Private" Methods; really, default implementations
	// Lifecycle
+ toolAwake:theApp
{
	const char *const * types;	
	int			i;

	[theApp   registerAnnotation: [eTImage class] 
							name: "eTImage"
					RTFDirective: "eTImage"
					   menuLabel: "Insert Image..."
						 menuKey: 'I'
						menuIcon: (NXImage *) nil];
	types = [NXImage imagePasteboardTypes];
	for(i=0; types[i]; i++)
		[theApp registerType:types[i] for:[eTImage class]];
	types = [NXImage imageFileTypes];
	for(i=0; types[i]; i++)
		[theApp registerType:NXCreateFileContentsPboardType(types[i])
				for:[eTImage class]];
	return self;
}
- init
{	
	[super init];
	if (!description){
		description = (char *)malloc(2*sizeof(char));
		strcpy(description, "");
	}
	isDraggable = YES;
	// everything else is automatically nil or NO
	imageComponent = altImageComponent = nil;
	captionMode = NO;
	return self;
}
- free
{	
	[[NXApp inspector] inspect:nil]; 
	[etDoc unregisterNotification:self];
	etDoc=nil;
	[imageComponent free];
	[altImageComponent free];
	return self = [super free];
}

- initFromPboard:thePB inDoc:theDoc linked:(BOOL) linked
{
	[self init];						// this is a wierd call-chain. check?
	etDoc = theDoc;
	theText = [[etDoc docUI] eTextObj]; // consistency checking
	[etDoc registerNotification:self];
	
	if ((!thePB) && !imageComponent) {
		// initialize as a null image as if from a menu selection
		imageComponent = [eTImageComponent newImageNamed:"eTImageComponentIcon"];
		[self setImageComponent:imageComponent];
	} else if (thePB) {
		imageComponent = [[eTImageComponent alloc] 
							initInDoc:theDoc linked:linked];
		[self setImageComponent:
			[imageComponent readComponentFromPboard:thePB]];
	}
	return self;
}

	// Drawing
- calcCellSize:(NXSize *)theSize
{
	if (captionMode) [self setCaptionMode]; //Recalcs font-sizes
	*theSize = size;
	if (usesButtonStyle) {
		theSize->width += 8; 
		theSize->height += 8;
	}
	return self;
}

- drawSelf:(const NXRect *)cellFrame inView:view
{
	NXPoint	point;
	NXRect  bounds;
	NXRun 	*theRun;
	
	if (!etDoc || !theText) {
		theText = view;
		etDoc = [theText etDoc];
		[etDoc registerNotification:self];
	}
	
	[view getBounds:&bounds];
    PSgsave();
    point = cellFrame->origin;
    point.y += cellFrame->size.height;
	if (usesButtonStyle) {
		NXDrawButton(cellFrame, &bounds);
		point.x += 4; point.y -= 4;
	} else {
		//if ([theText shouldDrawColor])
		//	NXSetColor([theText backgroundColor]);
		//else
			PSsetgray([theText backgroundGray]);
		NXRectFill(cellFrame);
	}
	if (captionMode)
		theRun = [theText runForAnnotation:self];
	if (captionMode && theRun) {
		if ([theText shouldDrawColor])
			NXSetColor([theText runColor:theRun]);
		else
			PSsetgray([theText runGray:theRun]);
		PSmoveto(point.x, point.y + (([theRun->font metrics])->descender * [theRun->font pointSize]));
		[theRun->font set];
		PSshow(description);
	} else {
		[((state && !usesButtonStyle) ? altImage : image) 
			composite:NX_SOVER toPoint:&point];
	}
	PSgrestore();
    return self;
}

- highlight:(const NXRect *)rect inView:view lit:(BOOL)flag
{
    if (highlighted != flag) {
		highlighted = flag;
		//NXHighlightRect(cellFrame);
    }
    return self;
}

- (BOOL)	trackMouse:(NXEvent *)theEvent
			inRect:(const NXRect *)cellFrame
			ofView:view
{	// See chapter 7, Modal Event Loops in Prog Dynamics
	NXPoint			point,offset,mLoc;
	NXRect			tracker, bounds;
    register int	inside;
    int				shouldLoop = YES;
    int				oldMask;
    NXEvent			*nextEvent,saveEvent;
    Pasteboard		*dragPasteboard;
	NXImage			*proxyImage;
	BOOL			inTextSel;	// you can't drag out of selected text.
	NXSelPt			begin, end;
	int				posn;
	NXRun			*theRun;
	
	[view getBounds:&bounds];
	tracker = *cellFrame;
	NXIntersectionRect(&bounds, &tracker);
	tracker.size.width += 16; tracker.size.height += 16; tracker.origin.x -=8; tracker.origin.y -= 8;
	point = cellFrame->origin;
	point.y += cellFrame->size.height;
    saveEvent = *theEvent;

	PSgsave();
	if (usesButtonStyle) {
		NXDrawGrayBezel(cellFrame, &bounds);
		point.x += 4; point.y -= 4;
	}

	if (captionMode)
		theRun = [theText runForAnnotation:self];
	if (captionMode && theRun) {
		if ([theText shouldDrawColor])
			NXSetColor([theText runColor:theRun]);
		else
			PSsetgray([theText runGray:theRun]);
		PSmoveto(point.x, point.y + (([theRun->font metrics])->descender * [theRun->font pointSize]));
		[theRun->font set];
		PSshow(description);
		NXHighlightRect(cellFrame);
	} else {
		if (usesButtonStyle)
			NXHighlightRect(cellFrame);
		else 
			[(state ? image : altImage) composite:NX_SOVER toPoint:&point];
	}

	PSgrestore();
	NXPing(); [[view window] flushWindow];

	[view getSel:&begin :&end];
	posn = [view positionForAnnotation:self];
	if ((posn >= begin.cp) && (posn <= end.cp)) inTextSel = YES;
	else inTextSel = NO;
    oldMask = [[view window] addToEventMask:NX_LMOUSEDRAGGEDMASK];
    while (shouldLoop) {
        nextEvent = 
			[NXApp getNextEvent:(NX_LMOUSEUPMASK|NX_LMOUSEDRAGGEDMASK)];
		mLoc = nextEvent->location;
		[view convertPoint:&mLoc fromView:nil];
		inside = [view mouse:&mLoc inRect:&tracker];
		if ((!inside) && isDraggable && !inTextSel) {	
			NXPoint imgLoc;
			NXSize	imgSize;
					
			dragPasteboard = [Pasteboard newName: NXDragPboard];
			[self drag:dragPasteboard image: &proxyImage];
			[proxyImage getSize:&imgSize];
			// proxyImage's (fictional) origin in Text coords
			imgLoc = saveEvent.location; 
			[view convertPoint:&imgLoc fromView:nil];
			imgLoc.x -= imgSize.width/2;
			imgLoc.y += imgSize.height/2;

			offset.x = nextEvent->location.x - saveEvent.location.x;
			offset.y = nextEvent->location.y - saveEvent.location.y;

			if (dragPasteboard && proxyImage) {
				[view 	dragImage:proxyImage
						at:&imgLoc offset:&offset
						event:&saveEvent pasteboard:dragPasteboard
						source:self slideBack: YES];
				shouldLoop = NO;
			}
			
			[self inspect:nextEvent];		
		}
		// ok, now is it inside the _actual_ bounds?
		inside = [view mouse:&mLoc inRect:cellFrame];
		if ((nextEvent->type == NX_LMOUSEUP) && shouldLoop && inside) {
			// command-click  only inspects
			// single-click inspects AND click:s
			// double click inspects AND click:s AND doubleClick:s
			if (nextEvent->data.mouse.click == 2)
				[self doubleClick:nextEvent];
			else if (nextEvent->data.mouse.click == 1) {
				[self inspect:nextEvent];
				if (!((nextEvent->flags) & NX_COMMANDMASK)) 
					[self click:nextEvent];
			}
			shouldLoop = NO;
		}
		if (nextEvent->type == NX_LMOUSEUP)
			shouldLoop = NO;	// mouse up outside rects.
    }
    [[view window] setEventMask:oldMask];

	PSgsave();
	if (usesButtonStyle)
		NXDrawButton(cellFrame,&bounds);
	else 
		NXEraseRect(cellFrame);

	if (captionMode)
		theRun = [theText runForAnnotation:self];
	if (captionMode && theRun) {
		if ([theText shouldDrawColor])
			NXSetColor([theText runColor:theRun]);
		else
			PSsetgray([theText runGray:theRun]);
		PSmoveto(point.x, point.y + (([theRun->font metrics])->descender * [theRun->font pointSize]));
		[theRun->font set];
		PSshow(description);
	} else {
		[((state || usesButtonStyle) ? altImage : image) 
			composite:NX_SOVER toPoint:&point];
	}

	PSgrestore();
	NXPing(); 
	[[view window] flushWindow];

	return YES;
}
				
	// File I/O
	// Here is a curious and inexplicable design decision:
	// altImageComponents are never written.
	// Why? I don't know. The implication is that the altImage is never the
	// explicitly set (or desired) by the user.
- readRichText:(NXStream *)stream forView:view
{
	int i,ver;
	char shouldCap = 0;
	NXSize temp;
	
	if (!etDoc || !theText) {
		theText = view;
		etDoc = [theText etDoc];
		[etDoc registerNotification:self];
	}
	NXScanf(stream, "%d ", &ver);
	if ((ver != _eTImageVERSION) && (ver != _eTImageVERSION_captions)) {
		// bad version block.
		NXLogError("eTImage found unparseable version %d at position %d",
					ver, NXTell(stream));
		return nil;
	}
	NXScanf(stream, "%f %f %d", &(temp.height), &(temp.width), &i);
	NXGetc(stream); // space-eater
	if (i) {
		if (description) free(description);
		description = malloc(sizeof(char)*(i+1));
		NXRead(stream, description, i);
		description[i] = '\0';
	}
	NXGetc(stream);		// balances out the space at the end of %s
	if (ver >= _eTImageVERSION_captions) {
		NXScanf(stream, "%c ", &shouldCap);
		shouldCap -= 'A';
	}
	[self setImageComponent:[[[eTImageComponent alloc] init] 
						readRichText:stream forView:view]];
	if (shouldCap) {
		//[self perform:@selector(setCaptionMode) with:nil afterDelay:0 cancelPrevious:NO];
		[self setCaptionMode];
		captionMode = YES;
	}
	return self;
}
- writeRichText:(NXStream *)stream forView:view
{
	if (!(size.height && size.width)) {
		//sanity check failed. try reaquiring size from the image
		image = [imageComponent theImage];
		[image getSize:&size];
		if (!(size.height && size.width)) {
			// now we have real problems. go undercover here:
			[imageComponent registerError];
			image = [imageComponent theImage];
			[image getSize:&size];
		}
	}
	NXPrintf(stream, "%d %f %f %d %s %c ", _eTImageVERSION_captions,
			size.height, size.width, strlen(description), description, captionMode + 'A');
	[imageComponent writeRichText:stream forView:view];
	return self;
}

- writeHTML:(NXStream *)stream forView:view {
	if (captionMode) {
		NXPrintf(stream, "%v", description);
	} else if (([self class] == [eTImage class]) && ![imageComponent isShared]) {
		// Make the .gif point to the original file
		NXPrintf(stream, "<A HREF=\"%V\">", [imageComponent componentName]);
		[imageComponent writeHTML:stream forView:view withCaption:description];
		NXPrintf(stream,"</A>");
	} else {
		[imageComponent writeHTML:stream forView:view withCaption:description];
	}
	return self;
}
- writeLaTeX:(NXStream *)stream forView:view
{
	if (captionMode) {
		NXPrintf(stream, "%W", description);
	} else {
		NXPrintf(stream,"\\begin{figure}\n\\hrule\n\\vspace{12 pt}\n");
		[imageComponent writeLaTeX:stream forView:view];
		NXPrintf(stream,"\n\\vspace{12 pt}\n\\hrule\n\\caption{\\em %w.}\n\\label{%w}\n\\end{figure}\n[Figure \\ref{%w}]", description, [imageComponent componentName],[imageComponent componentName]);
	}
	return self;
}
- writeASCIIRef:(NXStream *)stream forView:view
	{return [imageComponent writeASCIIRef:stream forView:view];}

	// Format encoding and support
- writeComponentToPath:(NXAtom)path inFormat:(int) theFormat
{
	if(!etDoc)	NXLogError("etDoc is nil at %s %u",__FILE__,__LINE__);
	if ((theFormat == ETFD_FMT) || !captionMode)
		[imageComponent writeComponentToPath:path inFormat:theFormat];
	if (([self class] == [eTImage class]) && (theFormat == HTMD_FMT) && !captionMode)
		[imageComponent writeComponentToPath:path inFormat:ETFD_FMT];
	return self;
}
	// Drag-and-Drop
- addToPboard:pboard
{
	[imageComponent addToPboard:pboard];
	return self;
}
- (NXDragOperation)draggingSourceOperationMaskForLocal:(BOOL)flag
{
	return (flag ? NX_DragOperationLink : NX_DragOperationAll);
}
- draggedImage:(NXImage *)image endedAt:(NXPoint *)screenPoint
	deposited:(BOOL)flag
{
	return self;
}
@end